package com.rein.study.designpattern.builder._01singleton;

/**
 * 双重锁机制
 * <p>
 * 线程安全，延迟初始化
 */
public class DoubleLockSingleton {
    //使用volatile避免JVM指令重排序

    // new关键字初始化一个对象的过程并不是一个原子的操作，它分成下面三个步骤进行：
    // 1、给 singleton 分配内存
    // 2、调用 Singleton 的构造函数来初始化成员变量
    // 3、将 singleton 对象指向分配的内存空间（执行完这步 singleton就为非 null 了）

    // 如果虚拟机存在指令重排序优化，则步骤2和3的顺序是无法确定的。
    // 如果A线程率先进入同步代码块并先执行了3而没有执行2，此时因为singleton已经非null。
    // 这时候线程B到了30处，判断singleton非null并将其返回使用，因为此时Singleton实际上还未初始化，自然就会出错。
    // sychronized可以解决内存可见性，但是不能解决重排序问题。
    private static volatile DoubleLockSingleton instance;

    //构造函数私有
    private DoubleLockSingleton() {
    }

    // 双重判断
    // 第一次判空，避免不必要的实例
    // 第二次判空，同步，避免多线程问题
    private static DoubleLockSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleLockSingleton.class) {
                if (instance == null) {
                    instance = new DoubleLockSingleton();
                }
            }
        }
        return instance;
    }
}
/**
 *  就算在单例类被实例化时有多个线程同时通过了第30行代码 if (instance == null) 的判断，
 *  但同一时间只有一个线程获得锁后进入临界区。通过第30行判断的每个线程会依次获得锁进入临界区，
 *  所以进入临界区后还要再判断一次单例类是否已被其它线程实例化，以避免多次实例化。
 *  由于双重加锁实现仅在实例化单例类时需要加锁，所以相较于简单锁懒汉模式实现方式会带来性能上的提升。
 *  另外需要注意的是双重加锁要对 instance 域加上 volatile 修饰符。
 *  由于 synchronized 并不是对 instance 实例进行加锁（因为现在还并没有实例），
 *  所以线程在执行完第33行修改 instance 的值后，应该将修改后的 instance 立即写入主存（main memory），
 *  而不是暂时存在寄存器或者高速缓冲区（caches）中，以保证新的值对其它线程可见。
 */